use std::rc::Rc;

use cgmath::{vec2, Matrix4, SquareMatrix, Vector2, Vector4};

use crate::{
    material::attribute::{Texture2D, Uniform},
    math::Rect2D,
    mesh::MeshInstance,
    shader::Shader,
    Brick, Context, Material, Pixmap, Scene,
};

use super::{builtin_shaders, vertices::{v2dcu, Vertex2DCU}};

pub struct SpriteBatch {
    pipeline: Rc<wgpu::RenderPipeline>,
    material: Material,
    mesh: MeshInstance<Vertex2DCU>,
}

impl SpriteBatch {
    pub fn new(ctx: &Context, scene: &Scene, pixmap: Pixmap) -> Self {
        let sampler = ctx.device.create_sampler(&wgpu::SamplerDescriptor {
            min_filter: wgpu::FilterMode::Nearest,
            mag_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        let texture = Texture2D::new(ctx, pixmap, wgpu::TextureFormat::Rgba8UnormSrgb);

        let material = Material::builder()
            .unifrom(ctx, Matrix4::<f32>::identity(), wgpu::ShaderStages::VERTEX)
            .attr(texture, wgpu::ShaderStages::FRAGMENT)
            .attr(sampler, wgpu::ShaderStages::FRAGMENT)
            .build(ctx);

        let shader = Shader::from_src::<Vertex2DCU>(ctx, builtin_shaders::SPIRTE_SRC);

        let layout =
            ctx.create_pipeline_layout(&[scene.global_layout(0).unwrap(), material.layout()]);

        let pipeline = ctx.create_simple_render_pipeline(
            &layout,
            &shader,
            wgpu::PrimitiveState {
                front_face: wgpu::FrontFace::Cw,
                ..Default::default()
            },
        );

        Self {
            pipeline: Rc::new(pipeline),
            mesh: MeshInstance::new(ctx),
            material,
        }
    }

    pub fn with_pipline_material(
        ctx: &Context,
        pipeline: Rc<wgpu::RenderPipeline>,
        mat: Material,
    ) -> Self {
        Self {
            pipeline,
            material: mat,
            mesh: MeshInstance::new(ctx),
        }
    }

    pub fn clear(&mut self) {
        self.mesh.clear();
    }

    pub fn add_sprite(
        &mut self,
        position: Vector2<f32>,
        width: f32,
        height: f32,
        region: Rect2D<f32>,
        color: Vector4<f32>,
    ) {
        let lb = v2dcu(position, color, region.left_bottom());
        let lt = v2dcu(position + vec2(0.0, height), color, region.left_top());
        let rb = v2dcu(position + vec2(width, 0.0), color, region.right_bottom());
        let rt = v2dcu(position + vec2(width, height), color, region.right_top());

        self.mesh.add_quad([lb, lt, rt, rb])
    }

    pub fn set_model_matrix(&mut self, m: Matrix4<f32>) {
        self.material
            .attr_mut::<Uniform<Matrix4<f32>>>(0)
            .unwrap()
            .set_data(m);
    }

    pub fn texture(&self) -> &Texture2D {
        self.material.attr(1).unwrap()
    }
}

impl Brick for SpriteBatch {
    fn prepare(&mut self, ctx: &Context) {
        self.material.prepare(ctx);
        self.mesh.prepare(ctx);
    }

    fn render<'pass>(&'pass self, pass: &mut wgpu::RenderPass<'pass>) {
        pass.set_pipeline(&self.pipeline);
        pass.set_bind_group(1, self.material.group(), &[]);
        self.mesh.render(pass);
    }
}
